package org.example.mq.client;

import org.example.mq.brokerserver.core.BasicProperties;
import org.example.mq.brokerserver.core.ExchangeType;
import org.example.mq.common.*;

import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class Channel {
    private String channelId;
    private Connection connection;
    //用来存储后续客户端收到的服务器的响应
    private ConcurrentHashMap<String, BasicReturnArguments> basicReturnMap = new ConcurrentHashMap<>();
    //如果当前channel订阅了某个队列，就需要在此记录对应回调是什么，当该队列的消息返回时就调用回调
    //此处约定一个channel只有一个回调函数
    private Consumer consumer = null;

    public Channel(String channelId,Connection connection){
        this.channelId = channelId;
        this.connection = connection;
    }

    //这个方法和服务器进行交换，告知服务器此处客户端创建新的channel了。
    public boolean createChannel() throws IOException {
        BasicArguments basicArguments = new BasicArguments();
        basicArguments.setChannelId(channelId);
        basicArguments.setRid(generateRid());
        byte[] payload = BinaryTool.toBytes(basicArguments);

        Request request = new Request();
        request.setType(0x1);
        request.setLength(payload.length);
        request.setPayload(payload);
        //发送请求
        connection.writeRequest(request);
        //等待服务器的响应
        BasicReturnArguments basicReturnArguments = waitResult(basicArguments.getRid());
        return basicReturnArguments.isOk();
    }

    private String generateRid() {
        return "R-" + UUID.randomUUID();
    }

    //这个方法实现阻塞等待服务器的响应
    private BasicReturnArguments waitResult(String rid) {
        BasicReturnArguments basicReturnArguments = null;
        while((basicReturnArguments = basicReturnMap.get(rid))==null){
            //如果为null证明还没响应还没送过来
            synchronized (this){
                try{
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //读取成功后删除这个消息
        basicReturnMap.remove(rid);
        return basicReturnArguments;
    }

    public void putReturns(BasicReturnArguments basicReturnArguments){
        basicReturnMap.put(basicReturnArguments.getRid(),basicReturnArguments);
        synchronized (this){
            //当前不知道多少线程在等待上述这个响应
            //所以要唤醒所有线程
            notifyAll();
        }
    }

    //关闭channel，发送给服务器一个0x2的请求
    public boolean close() throws IOException {
        BasicArguments basicArguments = new BasicArguments();
        basicArguments.setRid(generateRid());
        basicArguments.setChannelId(channelId);
        byte[] payload = BinaryTool.toBytes(basicArguments);

        Request request = new Request();
        request.setType(0x2);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(basicArguments.getRid());
        return basicReturnArguments.isOk();
    }

    //创建交换机
    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType,boolean durable,boolean autoDelete,
                                   Map<String,Object> options) throws IOException {
        ExchangeDeclareArguments exchangeDeclareArguments = new ExchangeDeclareArguments();
        exchangeDeclareArguments.setRid(generateRid());
        exchangeDeclareArguments.setExchangeName(exchangeName);
        exchangeDeclareArguments.setArgument(options);
        exchangeDeclareArguments.setChannelId(channelId);
        exchangeDeclareArguments.setExchangeType(exchangeType);
        exchangeDeclareArguments.setDurable(durable);
        exchangeDeclareArguments.setAutoDelete(autoDelete);
        byte[] payload = BinaryTool.toBytes(exchangeDeclareArguments);

        Request request = new Request();
        request.setLength(payload.length);
        request.setPayload(payload);
        request.setType(0x3);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(exchangeDeclareArguments.getRid());
        return basicReturnArguments.isOk();
    }

    //删除交换机
    public boolean exchangeDelete(String exchangeName) throws IOException {
        ExchangeDeleteArguments exchangeDeleteArguments = new ExchangeDeleteArguments();
        exchangeDeleteArguments.setRid(generateRid());
        exchangeDeleteArguments.setChannelId(channelId);
        exchangeDeleteArguments.setExchangeName(exchangeName);
        byte[] payload = BinaryTool.toBytes(exchangeDeleteArguments);

        Request request = new Request();
        request.setType(0x4);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(exchangeDeleteArguments.getRid());
        return basicReturnArguments.isOk();
    }

    //创建队列
    public boolean queueDeclare(String queueName,boolean durable,boolean exclusive,boolean autoDelete,
                                Map<String,Object> arguments) throws IOException {
        QueueDeclareArguments queueDeclareArguments = new QueueDeclareArguments();
        queueDeclareArguments.setQueueName(queueName);
        queueDeclareArguments.setChannelId(channelId);
        queueDeclareArguments.setDurable(durable);
        queueDeclareArguments.setExclusive(exclusive);
        queueDeclareArguments.setOptions(arguments);
        queueDeclareArguments.setRid(generateRid());
        queueDeclareArguments.setAutoDelete(autoDelete);
        byte[] payload = BinaryTool.toBytes(queueDeclareArguments);

        Request request = new Request();
        request.setType(0x5);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(queueDeclareArguments.getRid());
        return basicReturnArguments.isOk();
    }

    //销毁队列
    public boolean queueDelete(String queueName) throws IOException {
        QueueDeleteArguments arguments = new QueueDeleteArguments();
        arguments.setQueueName(queueName);
        arguments.setChannelId(channelId);
        arguments.setRid(generateRid());
        byte[] payload = BinaryTool.toBytes(arguments);

        Request request = new Request();
        request.setType(0x6);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(arguments.getRid());
        return basicReturnArguments.isOk();
    }

    //创建绑定
    public boolean queueBind(String queueName,String exchangeName,String bindingKey) throws IOException {
        QueueBindArguments arguments = new QueueBindArguments();
        arguments.setBindingKey(bindingKey);
        arguments.setChannelId(channelId);
        arguments.setRid(generateRid());
        arguments.setExchangeName(exchangeName);
        arguments.setQueueName(queueName);
        byte[] payload = BinaryTool.toBytes(arguments);

        Request request = new Request();
        request.setType(0x7);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(arguments.getRid());
        return basicReturnArguments.isOk();
    }

    //销毁绑定
    public boolean queueUnbind(String queueName,String exchangeName) throws IOException {
        QueueUnbindingArguments arguments = new QueueUnbindingArguments();
        arguments.setChannelId(channelId);
        arguments.setRid(generateRid());
        arguments.setQueueName(queueName);
        arguments.setExchangeName(exchangeName);
        byte[] payload = BinaryTool.toBytes(arguments);

        Request request = new Request();
        request.setType(0x8);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(arguments.getRid());
        return basicReturnArguments.isOk();
    }

    //发送消息
    public boolean basicPublish(String exchangeName, String routingKey, BasicProperties basicProperties,
                                byte[] body) throws IOException {
        BasicPublishArguments arguments = new BasicPublishArguments();
        arguments.setChannelId(channelId);
        arguments.setRid(generateRid());
        arguments.setBasicProperties(basicProperties);
        arguments.setRoutingKey(routingKey);
        arguments.setBody(body);
        arguments.setExchangeName(exchangeName);
        byte[] payload = BinaryTool.toBytes(arguments);

        Request request = new Request();
        request.setType(0x9);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(arguments.getRid());
        return basicReturnArguments.isOk();
    }

    //订阅消息
    public boolean basicConsume(String queueName,boolean autoAck,Consumer consumer) throws MqException, IOException {
        //先设置回调
        if(this.consumer!=null){
            throw new MqException("[Channel] 该channel已经设置了回调函数，不能重复设置！");
        }
        this.consumer = consumer;

        BasicConsumeArguments arguments = new BasicConsumeArguments();
        arguments.setRid(generateRid());
        arguments.setChannelId(channelId);
        arguments.setAutoAck(autoAck);
        arguments.setConsumerTag(channelId);//此处consumerTag也使用channelId来表示
        arguments.setQueueName(queueName);
        byte[] payload = BinaryTool.toBytes(arguments);

        Request request = new Request();
        request.setType(0xa);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(arguments.getRid());
        return basicReturnArguments.isOk();
    }

    //确认消息
    public boolean basicAck(String queueName,String messageId) throws IOException {
        BasicAckArguments arguments = new BasicAckArguments();
        arguments.setChannelId(channelId);
        arguments.setRid(generateRid());
        arguments.setMessageId(messageId);
        arguments.setQueueName(queueName);
        byte[] payload = BinaryTool.toBytes(arguments);

        Request request = new Request();
        request.setType(0xb);
        request.setPayload(payload);
        request.setLength(payload.length);

        connection.writeRequest(request);
        BasicReturnArguments basicReturnArguments = waitResult(arguments.getRid());
        return basicReturnArguments.isOk();
    }

    public String getChannelId() {
        return channelId;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public Connection getConnection() {
        return connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public ConcurrentHashMap<String, BasicReturnArguments> getBasicReturnMap() {
        return basicReturnMap;
    }

    public void setBasicReturnMap(ConcurrentHashMap<String, BasicReturnArguments> basicReturnMap) {
        this.basicReturnMap = basicReturnMap;
    }

    public Consumer getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer consumer) {
        this.consumer = consumer;
    }
}
